#pragma once

#include "include_gl.h"
#include <glm/glm.hpp>
#include "GLBuffer.h"
#include "GLTexture.h"
#include "GLFramebuffer.h"
#include "GLShader.h"
#include "GLProgram.h"
#include "GLVertexArray.h"
#include "constants.h"
#include "InputHandler.h"
#include "PCGSolver.h"
#include "FontRenderer.h"
#include "TextBox.h"

class App : public KeyEventListener, public WindowEventListener, public MouseEventListener
{
public:
	App();
	~App();

	int init(GLFWwindow* window, InputHandler* inputHandler);
	int loop();

	virtual void keyPressed(int key, int scancode, int mods);
	virtual void keyReleased(int key, int scancode, int mods);
	virtual void framebufferResized(int width, int height);
	virtual void wheelChanged(double x, double y);
	virtual void buttonPressed(int button, int mods);
	virtual void buttonReleased(int button, int mods);

private:
	GLFWwindow* _window;
	InputHandler* _inputHandler;
	PCGSolver* _pcgSolver;

	FontRenderer* _fontRenderer;
	TextBox* _numberTextBox;

	int _windowWidth, _windowHeight;
	float _windowRatio;
	bool _windowResized;

	double _startTime, _endTime, _interval;

	GLTexture* _levelSetTexture[2];
	int _levelSetTextureSrc;
	//GLFramebuffer* _levelSetFramebuffer;
	GLTexture* _levelSetTransferTexture;
	GLTexture* _levelSetStateTexture;
	GLBuffer* _levelSetActiveNodesBuffer[2];
	GLBuffer* _levelSetCounterBuffer;
	GLBuffer* _levelSetNewValuesBuffer;
	GLProgram* _levelSetProgram;
	struct {
		GLint viewProjMatrix;
	} _levelSetProgramUL;
	bool _reinitLevelSetNow;
	unsigned _framesSinceLastReinitCounter;
	unsigned _reinitLevelSetInterval;

	GLTexture* _velocityTexture[2];
	int _velocityTextureSrc;

	GLTexture* _jumpFloodingTexture[2];
	GLFramebuffer* _jumpFloodingFramebuffer;

	GLTexture* _nodeIndexTexture;

	GLBuffer* _zeroOnePositionBuffer;
	GLVertexArray* _zeroOneVertexArray;

	GLBuffer* _gridBuffer;
	GLVertexArray* _gridVertexArray;

	GLBuffer* _pointGridBuffer;
	GLVertexArray* _pointGridVertexArray;

	GLBuffer* _counterBuffer;

	GLBuffer* _femElementPositionsBuffer;
	GLBuffer* _sparseMatrixBuffer;
	GLBuffer* _bVectorBuffer;
	GLBuffer* _nodePositionBuffer;
	GLBuffer* _nodeNeighborhoodBuffer;
	GLBuffer* _xVectorBuffer;

	unsigned _numFemNodes, _numFemElements;

	glm::mat4 _viewMatrix;
	glm::mat4 _projMatrix;
	glm::mat4 _viewProjMatrix;
	float _zoomFactor;
	glm::vec2 _cameraCenter;


	glm::vec2 _cameraCenterStart;
	glm::vec2 _mousePositionStart;
	bool _cameraMoving;

	void initLevelSetTexture();
	void initLevelSetTransferTexture();
	void initVelocityTexture();
	void initZeroOneQuad();
	void initGrid();
	void initPointGrid();
	void updateViewMatrix();
	void updateProjMatrix();

	GLProgram* _markFluidNodesProgram;
	void extrapolateVelocities();

	void advectAndApplyForces();

	void enforceSolidBoundary();

	void buildFEMSystem();
	void solveFEMSystem();

	void projectPressure();

	void reinitLevelSet();

	GLProgram* _fluidNodesProgram;
	struct {
		GLint viewProjMatrix;
		GLint color;
	} _fluidNodesProgramUL;
	void drawFluidNodes();

	bool _drawFluidNodes;
	bool _drawGrid;
	bool _drawVelocities;
	bool _drawDebugTexture;

	bool _simulationRunning;
	bool _oneStepForward;

	GLProgram* _gridProgram;
	struct {
		GLint viewProjMatrix;
		GLint color;
	} _gridProgramUL;

	GLProgram* _velocityProgram;
	struct {
		GLint viewProjMatrix;
		GLint color;
	} _velocityProgramUL;

	GLProgram* _initJumpFloodingProgram;
	GLProgram* _jumpFloodingProgram;
	struct {
		GLint k;
	} _jumpFloodingProgramUL;

	GLProgram* _jumpFloodingFinalProgram;
	int _jumpFloodingSrc;

	GLProgram* _debugTextureProgram;
	struct {
		GLint viewProjMatrix;
	} _debugTextureProgramUL;

	GLProgram* _advectAndForceProgram;
	struct {
		GLint timeStep;
	} _advectAndForceProgramUL;

	GLProgram* _solidBoundaryProgram;
	GLProgram* _enumerateNodesProgram;
	GLProgram* _assembleFemSystemProgram;
	struct {
		GLint numFemElements;
	} _assembleFemSystemProgramUL;

	GLProgram* _generateNodeNeighborhoodProgram;
	struct {
		GLint numFemNodes;
	} _generateNodeNeighborhoodProgramUL;

	GLProgram* _projectPressureProgram;
	struct {
		GLint numFemNodes;
	} _projectPressureProgramUL;

	GLProgram* _clearDataProgram;
	struct {
		GLint value;
		GLint n;
	} _clearDataProgramUL;

	GLProgram* _clearImageR32iProgram;
	struct {
		GLint value;
		GLint bounds;
	}_clearImageR32iProgramUL;

	GLProgram* _clearImageRgba32fProgram;
	struct {
		GLint value;
		GLint bounds;
	}_clearImageRgba32fProgramUL;

	GLProgram* _levelSetMarkBoundaryProgram;

	GLProgram* _levelSetMarkUnmarkedNeighborsProgram;
	struct {
		GLint numFinishedNodes;
	} _levelSetMarkUnmarkedNeighborsProgramUL;

	GLProgram* _levelSetComputeEikonalProgram;
	struct {
		GLint numActiveNodes;
		GLint findNextActiveNodes;
	} _levelSetComputeEikonalProgramUL;

	GLProgram* _levelSetWriteNewValuesProgram;
	struct {
		GLint numActiveNodes;
	} _levelSetWriteNewValuesProgramUL;

	GLProgram* _levelSetCopyOldValuesProgram;

	void drawGrid();
	void drawLevelSets();
	void drawVelocities();
	void drawDebugTexture();
	void showNumbers();

	enum ShowOptions {
		SHOW_NOTHING,
		SHOW_PRESSURE,
		SHOW_NEIGHBORHOOD
	};

	ShowOptions _showOption;

	glm::vec2 cursorToTex(glm::vec2 cursor);
};